/*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * XStream.java
 * Copyright (C) 2008-2012 University of Waikato, Hamilton, New Zealand
 */

package weka.core.xml;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;

/**
 * This class is a helper class for XML serialization using
 * <a href="http://xstream.codehaus.org" target="_blank">XStream</a> . XStream
 * does not need to be present, since the class-calls are done generically via
 * Reflection.
 * 
 * @author Mark Hall (mhall{[at]}pentaho{[dot]}org)
 * @version $Revision$
 */
public class XStream {

    /**
     * indicates whether
     * <a href="http://xstream.codehaus.org" target="_blank">XStream</a> is present
     */
    protected static boolean m_Present = false;

    /** the extension for XStream files (including '.') */
    public final static String FILE_EXTENSION = ".xstream";

    /** check for XStream statically (needs only to be done once) */
    static {
        checkForXStream();
    }

    /**
     * checks whether the XStream is present in the class path
     */
    private static void checkForXStream() {
        try {
            Class.forName("com.thoughtworks.xstream.XStream");
            m_Present = true;
        } catch (Exception e) {
            m_Present = false;
        }
    }

    /**
     * returns whether XStream is present or not, i.e. whether the classes are in
     * the classpath or not
     * 
     * @return whether XStream is available
     */
    public static boolean isPresent() {
        return m_Present;
    }

    /**
     * Serializes the supplied object xml
     * 
     * @param toSerialize the object to serialize
     * @return the serialized object as an XML string
     * @throws Exception if something goes wrong
     */
    public static String serialize(Object toSerialize) throws Exception {
        Class<?> xstreamClass;
        java.lang.reflect.Constructor<?> constructor;
        Object xstream;
        Class<?>[] serializeArgsClasses = new Class[1];
        Object[] serializeArgs = new Object[1];
        java.lang.reflect.Method methodSerialize;
        String result;

        xstreamClass = Class.forName("com.thoughtworks.xstream.XStream");
        constructor = xstreamClass.getConstructor();
        xstream = constructor.newInstance();

        serializeArgsClasses[0] = Object.class;
        serializeArgs[0] = toSerialize;
        methodSerialize = xstreamClass.getMethod("toXML", serializeArgsClasses);

        // execute it
        try {
            result = (String) methodSerialize.invoke(xstream, serializeArgs);
        } catch (Exception ex) {
            result = null;
        }

        return result;
    }

    /**
     * writes the XML-serialized object to the given file
     * 
     * @param filename the file to serialize the object to
     * @param o        the object to write to the file
     * @return whether writing was successful or not
     * @throws Exception if something goes wrong while writing to the file
     */
    public static boolean write(String filename, Object o) throws Exception {
        return write(new File(filename), o);
    }

    /**
     * write the XML-serialized object to the given file
     * 
     * @param file the file to serialize the object to
     * @param o    the object to write to the file
     * @return whether writing was successful or not
     * @throws Exception if something goes wrong while writing to the file
     */
    public static boolean write(File file, Object o) throws Exception {
        return write(new BufferedOutputStream(new FileOutputStream(file)), o);
    }

    /**
     * writes the XML-serialized object to the given output stream
     * 
     * @param stream the output stream
     * @param o      the object to write
     * @return true if everything goes ok
     */
    public static boolean write(OutputStream stream, Object o) throws Exception {

        Class<?> xstreamClass;
        java.lang.reflect.Constructor<?> constructor;
        Object xstream;
        Class<?>[] serializeArgsClasses = new Class[2];
        Object[] serializeArgs = new Object[2];
        java.lang.reflect.Method methodSerialize;
        boolean result = false;

        xstreamClass = Class.forName("com.thoughtworks.xstream.XStream");
        constructor = xstreamClass.getConstructor();
        xstream = constructor.newInstance();

        serializeArgsClasses[0] = Object.class;
        serializeArgsClasses[1] = OutputStream.class;
        serializeArgs[0] = o;
        serializeArgs[1] = stream;
        methodSerialize = xstreamClass.getMethod("toXML", serializeArgsClasses);

        // execute it
        try {
            methodSerialize.invoke(xstream, serializeArgs);
            result = true;
        } catch (Exception ex) {
            result = false;
        }

        return result;
    }

    /**
     * writes the XML-serialized object to the given Writer.
     * 
     * @param writer      the Writer
     * @param toSerialize the object to write
     * @return true if everything goes ok
     * @throws Exception if something goes wrong
     */
    public static boolean write(Writer writer, Object toSerialize) throws Exception {
        Class<?> xstreamClass;
        java.lang.reflect.Constructor<?> constructor;
        Object xstream;
        Class<?>[] serializeArgsClasses = new Class[2];
        Object[] serializeArgs = new Object[2];
        java.lang.reflect.Method methodSerialize;
        boolean result = false;

        xstreamClass = Class.forName("com.thoughtworks.xstream.XStream");
        constructor = xstreamClass.getConstructor();
        xstream = constructor.newInstance();

        serializeArgsClasses[0] = Object.class;
        serializeArgsClasses[1] = Writer.class;
        serializeArgs[0] = toSerialize;
        serializeArgs[1] = writer;
        methodSerialize = xstreamClass.getMethod("toXML", serializeArgsClasses);

        // execute it
        try {
            methodSerialize.invoke(xstream, serializeArgs);
            result = true;
        } catch (Exception ex) {
            result = false;
        }

        return result;
    }

    /**
     * reads the XML-serialized object from the given file
     * 
     * @param filename the file to deserialize the object from
     * @return the deserialized object
     * @throws Exception if something goes wrong while reading from the file
     */
    public static Object read(String filename) throws Exception {
        return read(new File(filename));
    }

    /**
     * reads the XML-serialized object from the given file
     * 
     * @param file the file to deserialize the object from
     * @return the deserialized object
     * @throws Exception if something goes wrong while reading from the file
     */
    public static Object read(File file) throws Exception {
        return read(new BufferedInputStream(new FileInputStream(file)));
    }

    /**
     * reads the XML-serialized object from the given input stream
     * 
     * @param stream the input stream
     * @return the deserialized object
     * @throws Exception if something goes wrong while reading from stream
     */
    public static Object read(InputStream stream) throws Exception {
        Class<?> xstreamClass;
        java.lang.reflect.Constructor<?> constructor;
        Object xstream;
        Class<?>[] deSerializeArgsClasses = new Class[1];
        Object[] deSerializeArgs = new Object[1];
        java.lang.reflect.Method methodDeSerialize;
        Object result;

        xstreamClass = Class.forName("com.thoughtworks.xstream.XStream");
        constructor = xstreamClass.getConstructor();
        xstream = constructor.newInstance();

        deSerializeArgsClasses[0] = InputStream.class;
        deSerializeArgs[0] = stream;
        methodDeSerialize = xstreamClass.getMethod("fromXML", deSerializeArgsClasses);

        // execute it
        try {
            result = methodDeSerialize.invoke(xstream, deSerializeArgs);
        } catch (Exception ex) {
            ex.printStackTrace();
            result = null;
        }

        return result;
    }

    /**
     * reads the XML-serialized object from the given Reader
     * 
     * @param r the reader
     * @return the deserialized object
     * @throws Exception if something goes wrong while reading from stream
     */
    public static Object read(Reader r) throws Exception {
        Class<?> xstreamClass;
        java.lang.reflect.Constructor<?> constructor;
        Object xstream;
        Class<?>[] deSerializeArgsClasses = new Class[1];
        Object[] deSerializeArgs = new Object[1];
        java.lang.reflect.Method methodDeSerialize;
        Object result;

        xstreamClass = Class.forName("com.thoughtworks.xstream.XStream");
        constructor = xstreamClass.getConstructor();
        xstream = constructor.newInstance();

        deSerializeArgsClasses[0] = Reader.class;
        deSerializeArgs[0] = r;
        methodDeSerialize = xstreamClass.getMethod("fromXML", deSerializeArgsClasses);

        // execute it
        try {
            result = methodDeSerialize.invoke(xstream, deSerializeArgs);
        } catch (Exception ex) {
            ex.printStackTrace();
            result = null;
        }

        return result;
    }

    /**
     * Deserializes an object from the supplied XML string
     * 
     * @param xmlString the XML to deserialize from
     * @return the deserialized object
     * @throws Exception if something goes wrong
     */
    public static Object deSerialize(String xmlString) throws Exception {
        Class<?> xstreamClass;
        java.lang.reflect.Constructor<?> constructor;
        Object xstream;
        Class<?>[] deSerializeArgsClasses = new Class[1];
        Object[] deSerializeArgs = new Object[1];
        java.lang.reflect.Method methodDeSerialize;
        Object result;

        xstreamClass = Class.forName("com.thoughtworks.xstream.XStream");
        constructor = xstreamClass.getConstructor();
        xstream = constructor.newInstance();

        deSerializeArgsClasses[0] = String.class;
        deSerializeArgs[0] = xmlString;
        methodDeSerialize = xstreamClass.getMethod("fromXML", deSerializeArgsClasses);

        // execute it
        try {
            result = methodDeSerialize.invoke(xstream, deSerializeArgs);
        } catch (Exception ex) {
            ex.printStackTrace();
            result = null;
        }

        return result;
    }

}
